#include "gb.h"
#include <stdio.h>

unsigned char gb_read_mem(gb_state *gb, int address)
{
	switch(address & 0xf000)
	{
		// BIOS during startup, then becomes ROM0
		case 0x0000:
		// ROM0
		case 0x1000:
		case 0x2000:
		case 0x3000:
			return gb->rom[address];
		// ROM1
		case 0x4000:
		case 0x5000:
		case 0x6000:
		case 0x7000:
			return gb->rom[(address & 0x3fff) + gb->rom_offset];
		// VRAM (perform AND because of offset)
		case 0x8000:
		case 0x9000:
			return gb->vram[address & 0x1fff];
		// External RAM
		case 0xa000:
		case 0xb000:
			return gb->eram[(address & 0x1fff) + gb->eram_offset];
		// Working RAM
		case 0xc000:
		case 0xd000:
			return gb->wram[address & 0x1fff];
		// WRAM shadow
		case 0xe000:
			return gb->wram[address & 0x1fff];
		case 0xf000:
			switch(address & 0x0f00)
			{
				// WRAM shadow cont'd
				case 0x000:
				case 0x100:
				case 0x200:
				case 0x300:
				case 0x400:
				case 0x500:
				case 0x600:
				case 0x700:
				case 0x800:
				case 0x900:
				case 0xa00:
				case 0xb00:
				case 0xc00:
				case 0xd00:
					return gb->wram[address & 0x1fff];
				// Sprite Attribute Table OAM
				case 0xe00:
					// 0xfea0-0xfeff not usable
					if(address < 0xfea0)
						return gb->oam[address & 0xff];
					else
						return 0;
				// IO ports, High RAM, Interrupt Enable Register
				case 0xf00:
					// IO
					if(address < 0xff80)
					{
				        switch(address & 0xff)
                        {
							case 0x00:
                                gb_check_input(gb);
								return gb->P1;
                            case 0x05:
                                return gb->TIMA;
                            case 0x06:
                                return gb->TMA;
                            case 0x07:
                                return gb->TAC;
							case 0x0f:
								return gb->IF;
                            case 0x40:
                                return gb->gpu_state.LCDC;
                            case 0x41:
                                return gb->gpu_state.STAT;
                            case 0x42:
                                return gb->gpu_state.SCY;
                            case 0x43:
                                return gb->gpu_state.SCX;
                            case 0x44:
                                return gb->gpu_state.LY;
                            case 0x45:
                                return gb->gpu_state.LYC;
                            case 0x46:
                                // DMA port not readable
                                return 0;
                            case 0x47:
                                return gb->gpu_state.BGP;
                            case 0x48:
                                return gb->gpu_state.OBP0;
                            case 0x49:
                                return gb->gpu_state.OBP1;
                            case 0x4a:
                                return gb->gpu_state.WY;
                            case 0x4b:
                                return gb->gpu_state.WX;
                            case 0x4f:
                                return gb->gpu_state.VBK;
                            case 0x51:
                                return gb->gpu_state.HDMA1;
                            case 0x52:
                                return gb->gpu_state.HDMA2;
                            case 0x53:
                                return gb->gpu_state.HDMA3;
                            case 0x54:
                                return gb->gpu_state.HDMA4;
                            case 0x55:
                                return gb->gpu_state.HDMA5;
                            case 0x68:
                                return gb->gpu_state.BGPI;
                            case 0x69:
                                return gb->gpu_state.BGPD;
                            case 0x6a:
                                return gb->gpu_state.OBPI;
                            case 0x6b:
                                return gb->gpu_state.OBPD;
                        }
					}
					// HRAM
					else if(address < 0xffff)
					{
						return gb->hram[(address & 0xff) - 0x80];
					}
					// Interrupt Enable Register
					else
					{
						return gb->IE;
					}

			}
	}
	return 0;
}

void gb_write_mem(gb_state *gb, int address, unsigned char value)
{
	// Only used for DMA
	int srcAddr, dstAddr;
    unsigned char ramVal;
	switch(address & 0xf000)
	{
		// BIOS in startup, then becomes ROM0
		case 0x0000:
		// ROM0
		case 0x1000:
            switch(gb->ext)
			{
			    case ROM_ONLY:
                    break;
                case MBC1:
                case MBC1_RAM:
                case MBC1_RAM_BATT:
                case MBC2:
                case MBC2_BATT:
                case MBC3_TIM_BATT:
                case MBC3_TIM_RAM_BATT:
                case MBC3:
                case MBC3_RAM:
                case MBC3_RAM_BATT:
                    // Writing here simply enables or
                    //  disables external RAM. If the
                    //  ROM is correct, nothing needs
                    //  to be done here
                    break;
                case ROM_RAM:
                    break;
                case ROM_RAM_BATT:
                    break;
                case MMM01:
                    break;
                case MMM01_RAM:
                    break;
                case MMM01_RAM_BATT:
                    break;
                case MBC4:
                    break;
                case MBC4_RAM:
                    break;
                case MBC4_RAM_BATT:
                    break;
                case MBC5:
                    break;
                case MBC5_RAM:
                    break;
                case MBC5_RAM_BATT:
                    break;
                case MBC5_RUMB:
                    break;
                case MBC5_RUMB_RAM:
                    break;
                case MBC5_RUMB_RAM_BATT:
                    break;
                case CAM:
                    break;
                case TAMA5:
                    break;
                case HuC3:
                    break;
                case HuC1_RAM_BATT:
                    break;
			}
            return;
		case 0x2000:
		case 0x3000:
            switch(gb->ext)
			{
			    case ROM_ONLY:
                    break;
                case MBC1:
                case MBC1_RAM:
                case MBC1_RAM_BATT:
                case MBC2:
                case MBC2_BATT:
                case MBC3_TIM_BATT:
                case MBC3_TIM_RAM_BATT:
                case MBC3:
                case MBC3_RAM:
                case MBC3_RAM_BATT:
                    if(value == 0x00)
                        value = 0x01;
                    // Reset back to offset determined by reg0
                    // Memory banks are 16KB
                    gb->rom_offset -= (gb->mbc.reg1 * 1024 * 16);

                    gb->mbc.reg1 = value;
                    gb->rom_offset += (value * 1024 * 16);
                    break;
                case ROM_RAM:
                    break;
                case ROM_RAM_BATT:
                    break;
                case MMM01:
                    break;
                case MMM01_RAM:
                    break;
                case MMM01_RAM_BATT:
                    break;
                case MBC4:
                    break;
                case MBC4_RAM:
                    break;
                case MBC4_RAM_BATT:
                    break;
                case MBC5:
                    break;
                case MBC5_RAM:
                    break;
                case MBC5_RAM_BATT:
                    break;
                case MBC5_RUMB:
                    break;
                case MBC5_RUMB_RAM:
                    break;
                case MBC5_RUMB_RAM_BATT:
                    break;
                case CAM:
                    break;
                case TAMA5:
                    break;
                case HuC3:
                    break;
                case HuC1_RAM_BATT:
                    break;
			}
			return;
		// ROM1
		case 0x4000:
		case 0x5000:
            switch(gb->ext)
			{
			    case ROM_ONLY:
                    break;
                case MBC1:
                case MBC1_RAM:
                case MBC1_RAM_BATT:
                    // This really writes to a 2-bit register (reg2),
                    //  but it is easier to just calculate the offset
                    //  it represents
                    value &= 0x03;
                    // ROM select mode
                    if(gb->mbc.reg3 == 0x00)
                    {
                        // value selects a starting bank of either 0x00,
                        //  0x20, 0x40, or 0x60
                        gb->rom_offset = (value * 0x20 * 1024 * 16);
                        gb->rom_offset += (gb->mbc.reg1 * 1024 * 16);
                    }
                    // RAM select mode
                    else if(gb->mbc.reg3 == 0x01)
                    {
                        // Select which 8KB bank to use
                        gb->eram_offset = (value * 1024 * 8);
                    }
                    break;
                case MBC2:
                case MBC2_BATT:
                    break;
                case ROM_RAM:
                    break;
                case ROM_RAM_BATT:
                    break;
                case MMM01:
                    break;
                case MMM01_RAM:
                    break;
                case MMM01_RAM_BATT:
                    break;
                case MBC3_TIM_BATT:
                case MBC3_TIM_RAM_BATT:
                case MBC3:
                case MBC3_RAM:
                case MBC3_RAM_BATT:
                    // RAM bank select
                    if(value <= 0x03)
                    {
                        gb->eram_offset = (value * 1024 * 8);
                    }
                    // RTC counter select
                    else
                    {
                        //TODO: maps RTC counter into memory
                        // at 0xA000-0xBFFF
                    }
                    break;
                case MBC4:
                    break;
                case MBC4_RAM:
                    break;
                case MBC4_RAM_BATT:
                    break;
                case MBC5:
                    break;
                case MBC5_RAM:
                    break;
                case MBC5_RAM_BATT:
                    break;
                case MBC5_RUMB:
                    break;
                case MBC5_RUMB_RAM:
                    break;
                case MBC5_RUMB_RAM_BATT:
                    break;
                case CAM:
                    break;
                case TAMA5:
                    break;
                case HuC3:
                    break;
                case HuC1_RAM_BATT:
                    break;
			}
			return;
		case 0x6000:
		case 0x7000:
            switch(gb->ext)
			{
			    case ROM_ONLY:
                    break;
                case MBC1:
                case MBC1_RAM:
                case MBC1_RAM_BATT:
                    // reg3 is one bit
                    value &= 0x01;
                    gb->mbc.reg3 = value;
                    break;
                case MBC2:
                case MBC2_BATT:
                    break;
                case ROM_RAM:
                    break;
                case ROM_RAM_BATT:
                    break;
                case MMM01:
                    break;
                case MMM01_RAM:
                    break;
                case MMM01_RAM_BATT:
                    break;
                case MBC3_TIM_BATT:
                case MBC3_TIM_RAM_BATT:
                case MBC3:
                case MBC3_RAM:
                case MBC3_RAM_BATT:
                    if(gb->mbc.reg3 == 0x00 &&
                       value == 0x01)
                    {
                        //TODO: writing zero then one freezes the RTC registers
                        // so that they may be read. The clock continues running.
                        // Writing zero then one again unfreezes the registers
                    }
                    gb->mbc.reg3 = value;
                    break;
                case MBC4:
                    break;
                case MBC4_RAM:
                    break;
                case MBC4_RAM_BATT:
                    break;
                case MBC5:
                    break;
                case MBC5_RAM:
                    break;
                case MBC5_RAM_BATT:
                    break;
                case MBC5_RUMB:
                    break;
                case MBC5_RUMB_RAM:
                    break;
                case MBC5_RUMB_RAM_BATT:
                    break;
                case CAM:
                    break;
                case TAMA5:
                    break;
                case HuC3:
                    break;
                case HuC1_RAM_BATT:
                    break;
			}
			return;
		// VRAM (perform AND because of offset)
		case 0x8000:
		case 0x9000:
			gb->vram[address & 0x1fff] = value;
			return;
		// External RAM
		case 0xa000:
		case 0xb000:
			gb->eram[(address & 0x1fff) + gb->eram_offset] = value;
			return;
		// Working RAM
		case 0xc000:
		case 0xd000:
			gb->wram[address & 0x1fff] = value;
			return;
		// WRAM shadow
		case 0xe000:
			gb->wram[address & 0x1fff] = value;
			return;
		case 0xf000:
			switch(address & 0x0f00)
			{
				// WRAM shadow cont'd
				case 0x000:
				case 0x100:
				case 0x200:
				case 0x300:
				case 0x400:
				case 0x500:
				case 0x600:
				case 0x700:
				case 0x800:
				case 0x900:
				case 0xa00:
				case 0xb00:
				case 0xc00:
				case 0xd00:
					gb->wram[address & 0x1fff] = value;
					return;
				// Sprite Attribute Table OAM
				case 0xe00:
					// 0xfea0-0xfeff not usable
					if(address < 0xfea0)
					{
						gb->oam[address & 0xff] = value;
						return;
					}
					else
						return;
				// IO ports, High RAM, Interrupt Enable Register
				case 0xf00:
					// IO
					if(address < 0xff80)
					{
						switch(address & 0x00ff)
						{
							case 0x00:
								// Only bits 4-5 writable,
								//  bits 6-7 not used
								value &= 0xf0;
								gb->P1 = value | (gb->P1 & 0x0f);
								return;
                            case 0x05:
                                gb->TIMA = value;
                                return;
                            case 0x06:
                                gb->TMA = value;
                                return;
                            case 0x07:
                                gb->TAC = value;
                                return;
							case 0x0f:
								gb->IF = value;
								return;
                            case 0x40:
                                gb->gpu_state.LCDC = value;
                                return;
                            case 0x41:
								// Only bits 3-6 are writable
								value &= 0xf8;
								gb->gpu_state.STAT &= 0x07;
                                gb->gpu_state.STAT |= value;
                                return;
                            case 0x42:
                                gb->gpu_state.SCY = value;
                                return;
                            case 0x43:
                                gb->gpu_state.SCX = value;
                                return;
                            case 0x44:
								// Writing resets
                                gb->gpu_state.LY = 0x00;
                                return;
                            case 0x45:
                                gb->gpu_state.LYC = value;
                                return;
                            case 0x46:
                                // Writes 128 bytes from start address =
                                //  (value << 8) to OAM memory
								srcAddr = value << 8;
                                for(dstAddr=OAM_START;
										dstAddr < OAM_START + OAM_SIZE;
                                        dstAddr++, srcAddr++)
                                {
                                    ramVal = gb_read_mem(gb, srcAddr);
                                    gb_write_mem(gb, dstAddr, ramVal);
                                }
                                return;
                            case 0x47:
                                gb->gpu_state.BGP = value;
                                return;
                            case 0x48:
                                gb->gpu_state.OBP0 = value;
                                return;
                            case 0x49:
                                gb->gpu_state.OBP1 = value;
                                return;
                            case 0x4a:
                                gb->gpu_state.WY = value;
                                return;
                            case 0x4b:
                                gb->gpu_state.WX = value;
                                return;
                            case 0x4f:
                                gb->gpu_state.VBK = value;
                                return;
                            case 0x51:
                                gb->gpu_state.HDMA1 = value;
                                return;
                            case 0x52:
                                gb->gpu_state.HDMA2 = value;
                                return;
                            case 0x53:
                                gb->gpu_state.HDMA3 = value;
                                return;
                            case 0x54:
                                gb->gpu_state.HDMA4 = value;
                                return;
                            case 0x55:
                                gb->gpu_state.HDMA5 = value;
                                return;
                            case 0x68:
                                gb->gpu_state.BGPI = value;
                                return;
                            case 0x69:
                                gb->gpu_state.BGPD = value;
                                return;
                            case 0x6a:
                                gb->gpu_state.OBPI = value;
                                return;
                            case 0x6b:
                                gb->gpu_state.OBPD = value;
                                return;
						}
					}
					// HRAM
					else if(address < 0xffff)
					{
						gb->hram[(address & 0xff) - 0x80] = value;
						return;
					}
					// Interrupt Enable Register
					else
					{
                        gb->IE = value;
						return;
					}

			}
	}
	return;
}
